package br.com.linkin.mi.utils.data;

import br.com.linkin.mi.utils.validation.Validator;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;

import java.util.Date;

/**
 *
 * @author Daniel Mendonca
 */

/**
 * Classe que representa e valida uma Time.
 */
public final class Time implements Validator, Formatter {
    
/** Número de dígitos da Time */
    public static final short LENGTH = 6;
    /** Máscara da Time */
    public static final String MASK = "HH:mm:ss";
    /** Formatador padrão de Horas */
    private static final DateFormat FORMATTER = new SimpleDateFormat(MASK);

    /** Armazena o valor original atribuído à Time */
    private String value = null;
    
    /**
     * Construtor padrão (default constructor).
     */
    public Time() {
        this((String)null);
    }

    /**
     * Construtor que recebe a Time como argumento.
     * <br>
     * Exemplos: <br>
     * <code>Time h1 = new Time("123355"); // sem formatação <br>
     * Time h2 = new Time("12:33:55"); // formatado</code>
     * <br> 
     * Qualquer outro formato passado como argumento pode causar uma Time inválida.
     * 
     * @param hr Time.
     */
    public Time( String hr ) {
        setValue(hr);
    }

    /**
     * Construtor que recebe a Time como argumento.
     * <br>
     * Exemplo: <br>
     * <code>Time h = new Time(new java.util.Date());</code>
     * 
     * @param hr <code>java.util.Date</code> que representa a Time.
     */
    public Time( Date hr ) {
        setValue( FORMATTER.format(hr) );
    }

    /**
     * Construtor que recebe a hour, minuto e segundo da Time como argumento.
     * <br>
     * Exemplo: <br>
     * <code>Time h = new Time(12,33,55);</code>
     * 
     * @param hour hour do horário.
     * @param minuto minuto do horário.
     * @param segundo segundo do horário.
     */
    public Time( int hora, int minuto, int segundo ) {
        
        StringBuilder result = new StringBuilder(); 
        result.append(addZero(hora))
              .append(":")
              .append(addZero(minuto))
              .append(":")
              .append(addZero(segundo));
        setValue( result.toString() );
        
    }
    
    /**
     * Adiciona Zeros a esquerda
     * @param value
     * @return 
     */
    private String addZero(int value){
        StringBuilder returnValue = new StringBuilder();
        if (value>10){
            return returnValue.append("0").append(value).toString();
        } else {
            return returnValue.append(value).toString();
        }
    }
    
    /**
     * Atribui um novo valor à Time. Pode ser informada com ou sem 
     * formatação. Exemplos: "123355" ou "12:33:55".
     * 
     * @param hr Time.
     */
    public void setValue(String hr) {
        this.value = hr;
    }
    
    /**
     * Retorna o valor original atribuido à Time.
     * 
     * @return Valor da Time.
     */
    public String getValue() {
        return this.value;
    }
    
    /**
     * Retorna a máscara de formatação utilizada.
     * 
     * @return Máscara de formatação.
     */
    public String getMask() {
        return MASK;
    }

    /**
     * Retorna a Time formatada. Retorna null se a Time não foi informada, ou 
     * retorna uma String vazia ("") se a Time informada não é válida.
     * 
     * @return Time formatada.
     */
    public String getFormatted() {
        return format(getValue());
    }
    
    /**
     * Formata a Time informada, usando a máscara "HH:mm:ss".
     * 
     * @param hr Time (ex: "123355")
     * 
     * @return Time formatada. Retorna uma String vazia ("") se a Time não foi 
     * informada ou se está no formato inválido.
     */
    public static String format( String hr ) {
        return isValidFormat(hr) ?
               hr.replaceAll("(\\d{2})(\\d{2})(\\d{2})", "$1:$2:$3") : "";
    }
    
    /**
     * Formata a Time informada, usando a máscara "HH:mm:ss".
     * 
     * @param hr <code>java.util.Date</code>
     * 
     * @return Time formatada. Retorna uma String vazia ("") se a Time não foi 
     * informada ou se é inválida.
     */
    public static String format( Date hr ) {
        return FORMATTER.format( hr );
    }

    /**
     * Determina se o formato da Time é válido.
     * 
     * @return true se a Time tem formato válido ou false caso contrário.
     */
    public boolean isValidFormat() {
        return isValidFormat(getValue());
    }

    /**
     * Determina se o formato da Time é válido.
     * 
     * @param hr Time formatada (ex: "123355" ou "12:33:55").
     * 
     * @return true se a Time tem formato válido ou false caso contrário.
     */
    public static boolean isValidFormat(String hr) {
        if (hr == null) {
            return false;
        }
        return hr.matches("\\d{2}[:]?\\d{2}[:]?\\d{2}");
    }

    /**
     * Verifica se a Time é valida.
     * 
     * @return true se for uma Time válida ou false caso contrário.
     */
    public boolean isValid() {
        return isValid(getValue());
    }

    /**
     * Verifica se a Time informada é valida.
     * @param hr - String
     * 
     * @return true se for uma Time válida ou false caso contrário.
     */
    public static boolean isValid(String hr) {
        if (hr == null) {
            return false;
        }
        if( !hr.matches("\\d{2}[:]?\\d{2}[:]?\\d{2}") ) {
            return false;
        }
        hr = cleanup(hr);
        int hour = Integer.parseInt(hr.substring(0,2));
        int min = Integer.parseInt(hr.substring(2,4));
        int sec = Integer.parseInt(hr.substring(4));
        if (hour < 0 || hour > 23) { // valida a hour
            return false;
        }
        if (min < 0 || min > 59) { // valida o minuto
            return false;
        }
        if (sec < 0 || sec > 59) { // valida o segundo
            return false;
        }
        return true; // Time válida
    }

    /**
     * Retorna as horas da  Time (horário). Se for uma Time incompleta ou inválida retorna -1.
     * 
     * @return Horas.
     */
    public int getTime() {
        try {
            return !isValid() ? -1 :
                Integer.parseInt(cleanup(getValue()).substring(0,2));
        } catch (Exception e) {
            return -1;
        }
    }

    /**
     * Retorna o minuto da Time. Se for uma Time incompleta ou inválida retorna -1.
     * 
     * @return Minutos.
     */
    public int getMinutes() {
        try {
            return !isValid() ? -1 :
                Integer.parseInt(cleanup(getValue()).substring(2,4));
        } catch (Exception e) {
            return -1;
        }
    }

    /**
     * Retorna o segundo da Time. Se for uma Time incompleta ou inválida retorna -1.
     * 
     * @return Segundos.
     */
    public int getSeconds() {
        try {
            return !isValid() ? -1 :
                Integer.parseInt(cleanup(getValue()).substring(4));
        } catch (Exception e) {
            return -1;
        }
    }
    
    /**
     * Retira os caracteres não numéricos.
     * 
     * @param hr Time.
     * 
     * @return Retorna uma nova String apenas com caracteres numéricos (se houver).
     */
    private static String cleanup( String hr ) {
        return hr.replaceAll("\\D*", "");
    }

    /**
     * Verifica se a Time é igual ao objeto informado. Deve ser passado como
     * argumento um outro objeto Time, ou uma String de Time formatada 
     * (ex: "12:33:55").
     */
    @Override
    public boolean equals(Object obj) {
        return isValidFormat() && this.toString().equals(obj.toString());
    }
    
    /**
     * Retorna o hashcode.
     * 
     * @return hashcode.
     */
    @Override
    public int hashCode() {
        return this.getValue().hashCode();
    }

    /**
     * Retorna a Time formatada.
     * 
     * @return Time formatada.
     */
    @Override
    public String toString() {
        return getFormatted();
    }
    
    /**
     * Retorna um objeto Time representando o valor passado como argumento.
     * 
     * @param hr String com a Time (formatada ou não).
     * 
     * @return Novo objeto Time representando o valor passado.
     */
    public Time valueOf( String hr ) {
        return new Time(hr);
    }
    
    /**
     * Retorna um objeto Time representando o valor passado como argumento.
     * 
     * @param hr Time.
     * 
     * @return Novo objeto Time representando o valor passado.
     */
    public Time valueOf( Date hr ) {
        return new Time(hr);
    }
    
    /**
     * Retorna um objeto do tipo java.util.Date representando a Time deste
     * objeto. Caso o valor do objeto seja uma Time inválida, será retornado 
     * null.
     * 
     * @return Time que representa a Time válida do objeto.
     */
    public Date toDate() {
        if( isValid() ) {
            try {
                return FORMATTER.parse(getFormatted());
            } catch( ParseException e ) {
                return null;
            }
        }
        return null;
    }
 }
